package model.impl.network;

import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;

import module.CoreModule;

import org.jdom2.JDOMException;
import org.resotc.dao.CentroidDAO;
import org.resotc.dao.IntersectionDAO;
import org.resotc.dao.StreetDAO;
import org.resotc.model.Centroid;
import org.resotc.model.Element;
import org.resotc.model.ElementType;
import org.resotc.model.Individual;
import org.resotc.model.Intersection;
import org.resotc.model.Lane;
import org.resotc.model.NeighbourOrientation;
import org.resotc.model.Street;

import sim.engine.SimState;
import sim.engine.Steppable;
import sim.util.Double2D;

import com.google.inject.Guice;
import com.google.inject.Injector;

import config.Configuration;
import dao.impl.CentroidDAOImpl;
import dao.impl.IntersectionDAOImpl;
import dao.impl.StreetDAOImpl;

@SuppressWarnings("serial")
public abstract class ElementImpl implements Steppable, Element {

	// Where is this element located?
	private double x;
	private double y;

	// attributes
	private double length;

	// ID
	private int id;

	// Type
	private ElementType myType;

	// Capacity
	private double capacity;
	private double tCapacity;

	private HashMap<NeighbourOrientation, Element> neighbours;
	private HashMap<NeighbourOrientation, Element> destinations;
	private HashMap<NeighbourOrientation, Element> linkToNeighbours;
	private HashMap<NeighbourOrientation, Element> linkToDestinations;
	private IntersectionDAO intersectionDAO;
	private CentroidDAO centroidDAO;
	private Element successor;
	private Element predecessor;

	private ArrayList<Element> elements;

	Injector injector;

	/**
	 * Constructor
	 * 
	 * @param xPos
	 * @param yPos
	 * @param id
	 */
	public ElementImpl(int id, ElementType type, double x, double y) {
		super();
		this.x = x;
		this.y = y;
		this.id = id;
		this.myType = type;
		this.length = 0;

		// Capacities
		if (this.myType == ElementType.STREET) {
			capacity = Configuration.streetCapacity;
			tCapacity = Configuration.streetCapacity;
		} else if (this.myType == ElementType.INTERSECTION) {
			capacity = Configuration.junctionCapacity;
			tCapacity = Configuration.junctionCapacity;
		} else if (this.myType == ElementType.CENTROID) {
			capacity = -1;
			tCapacity = -1;
		} else {
			// System.err.println("ERROR: Element with ID " + this.id +
			// " has invalid type and cannot set capacities!");
			capacity = 0;
			tCapacity = 0;
		}

		neighbours = new HashMap<NeighbourOrientation, Element>();
	}

	public ElementImpl(int id, ElementType type) {
		super();

		this.id = id;
		this.myType = type;
		this.length = 0;

		// Capacities
		if (this.myType == ElementType.STREET) {
			capacity = Configuration.streetCapacity;
			tCapacity = Configuration.streetCapacity;
		} else if (this.myType == ElementType.INTERSECTION) {
			capacity = Configuration.junctionCapacity;
			tCapacity = Configuration.junctionCapacity;
		} else if (this.myType == ElementType.CENTROID) {
			capacity = -1;
			tCapacity = -1;
		} else {
			// System.err.println("ERROR: Element with ID " + this.id +
			// " has invalid type and cannot set capacities!");
			capacity = 0;
			tCapacity = 0;
		}

		destinations = new HashMap<NeighbourOrientation, Element>();
		linkToNeighbours = new HashMap<NeighbourOrientation, Element>();
		linkToDestinations = new HashMap<NeighbourOrientation, Element>();

		ArrayList<Element> elements = new ArrayList<Element>();
//		Injector injector = Guice.createInjector(new CoreModule());
//		centroidDAO = injector.getInstance(CentroidDAOImpl.class);
//		intersectionDAO = injector.getInstance(IntersectionDAOImpl.class);
	}

	/**
	 * @see Element#getPosition()
	 */
	public Double2D getPosition() {
		return new Double2D(x, y);
	}

	/**
	 * @see Element#setY(double)
	 */
	public void setX(double x) {
		this.x = x;
	}

	/**
	 * @see Element#setY(double)
	 */
	public void setY(double y) {
		System.out.println("y elementimpl" + x);
		this.y = y;
		System.out.println("y elementimpl this " + this.y );
	}

	/**
	 * @see Element#getX()
	 */
	public double getX() {
		return this.x;
	}

	/**
	 * @see Element#getY()
	 */
	public double getY() {
		return this.y;
	}

	/**
	 * @see Element#getElementType()
	 */
	public ElementType getElementType() {
		return myType;
	}

	/**
	 * @see Element#setElementType(ElementType)
	 */
	public void setElementType(ElementType type) {
		this.myType = type;
	}

	/**
	 * @see Element#getElement(Element)
	 */
	public Element getElement(Element element) {
		Injector injector = Guice.createInjector(new CoreModule());
		IntersectionDAO intersectionDAO = injector.getInstance(IntersectionDAOImpl.class);
		CentroidDAO centroidDAO = injector.getInstance(CentroidDAOImpl.class);

		ElementType type = element.getElementType();
		if (type == ElementType.INTERSECTION) {
			Intersection intersection = intersectionDAO.getIntersections().get(element.getId());
			return intersection;
		} else {
			Centroid centroid = centroidDAO.getCentroids().get(element.getId());
			return centroid;
		}
	}

	public void addArrivingIndividual(Individual newInd, NeighbourOrientation n) {
		// TODO;
	}

	public double getCapacity() {
		return this.capacity;
	}

	public void modifyCapacity(double decreaseRate) {
		if (decreaseRate >= 0 && decreaseRate <= 1) {
			this.capacity = decreaseRate * this.tCapacity;
		} else {
			System.err.println("ERROR: Cannot decrease capacity, received invalid decrease rate!");
		}
	}

	public void setInitialCapacity(double cap) {
		this.capacity = cap;
		this.tCapacity = cap;
	}

	public int getNumberOfIndividuals() {
		// TODO
		return 0;
	}

	public double getEstimatedLength() {
		// TODO
		return 0;
	}

	public double getLength() {
		return length;
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see network.Element#setLength(double)
	 */
	public void setLength(double length) {
		this.length = length;
	}

	public boolean isElementCrowded(Element element, NeighbourOrientation or) {
		// TODO
		return false;
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see network.Element#setElementCrowded(boolean,
	 * org.resotc.model.NeighbourOrientation)
	 */
	public void setElementCrowded(boolean isElementCrowded, NeighbourOrientation or) {

	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see network.Element#estimateDensity()
	 */
	public double estimateDensity() {
		// TODO
		return 0;
	}

	public void addNeighbour(NeighbourOrientation or, Element neighbour, Element linkToNeighbour) {
		addAndUpdateNeighbour(or, neighbour, linkToNeighbour);
	}

	/**
	 * 
	 */
	public void updateNeighbour(NeighbourOrientation or, Element neighbour, Element linkToNeighbour) {
		addAndUpdateNeighbour(or, neighbour, linkToNeighbour);
	}

	/**
	 * @see Intersection#addAndUpdateNeighbour(NeighbourOrientation, Element,
	 *      Element)
	 */
	public void addAndUpdateNeighbour(NeighbourOrientation or, Element neighbour, Element linkToNeighbour) {
		if (neighbour instanceof Intersection) {
			neighbours.put(or, linkToNeighbour);
			linkToNeighbours.put(or, linkToNeighbour);
		} else if (neighbour instanceof Centroid) {
			destinations.put(or, linkToNeighbour);
			linkToDestinations.put(or, linkToNeighbour);
		}
	}

	/**
	 * @see Element#determineNeighbours()
	 */
	public HashMap<NeighbourOrientation, Element> determineNeighbours() {
		HashMap<NeighbourOrientation, Element> neighbours = new HashMap<NeighbourOrientation, Element>();

		Injector injector = Guice.createInjector(new CoreModule());
		StreetDAO streetDAO = injector.getInstance(StreetDAOImpl.class);
		HashMap<Integer, Street> streets = streetDAO.getStreets();
		List<Street> streetList = new ArrayList<Street>(streets.values());

		// for (Street street : streetList) {
		// if (street.getPredecessor().equals(this)) {
		// NeighbourOrientation or =
		// NeighbourOrientation.getRelativeNeighbourOrientation(this.getPosition(),
		// street.getSuccessor().getPosition());
		// addNeighboursAndDestinations(or, this);
		// } else if (street.getSuccessor().equals(this)) {
		// NeighbourOrientation or =
		// NeighbourOrientation.getRelativeNeighbourOrientation(this.getPosition(),
		// street.getPredecessor().getPosition());
		// addNeighboursAndDestinations(or, this);
		// }
		//
		// }
		return neighbours;
	}

	private void addNeighboursAndDestinations(NeighbourOrientation or, Element element) {
		if (element.getElementType() == ElementType.INTERSECTION) {
			neighbours.put(or, element);
		} else if (element.getElementType() == ElementType.CENTROID) {
			destinations.put(or, element);
		}
	}

	// public HashMap<NeighbourOrientation, Element> determineNeighbours(Element
	// node) {
	// // We need a list of all existing Elements to determine which is a
	// // direct neighbour
	// HashMap<NeighbourOrientation, Element> neighbourHashMap = new
	// HashMap<NeighbourOrientation, Element>();
	//
	// try {
	// StreetDAO streetDAO = new StreetDAOImpl();
	// HashMap<Integer, Street> streets = streetDAO.getStreets();
	// List<Street> streetList = new ArrayList<Street>(streets.values());
	//
	// for (Street street : streetList) {
	// if (street.getPredecessor().getId() == node.getId()) {
	// NeighbourOrientation or =
	// NeighbourOrientation.getRelativeNeighbourOrientation(node.getPosition(),
	// street.getSuccessor().getPosition());
	// neighbourHashMap.put(or, street.getSuccessor());
	// }
	// }
	//
	// } catch (JDOMException e) {
	// // TODO Auto-generated catch block
	// e.printStackTrace();
	// } catch (IOException e) {
	// // TODO Auto-generated catch block
	// e.printStackTrace();
	// }
	// return neighbourHashMap;
	// }

	// public boolean isNeighbour(Element neighbour, Street linkToNeighbour) {
	// if (neighbour.getId() == linkToNeighbour.getSuccessor().getId()) {
	// return true;
	// } else {
	// return false;
	// }
	// }

	public boolean isNeighbour(Element neighbour) {
		
		return false;
	}

	public HashMap<NeighbourOrientation, Element> getNeighbours() {
		return neighbours;
	}

	public HashMap<NeighbourOrientation, Element> getDestinations() {
		return destinations;
	}

	public void addElement(Element element) {
		elements.add(element);
	}

	public List<Element> getElements() {
		return elements;
	}

	public int getId() {
		return id;
	}

	public void step(SimState state) {
		// TODO Auto-generated method stub

	}

	public boolean equals(Object o) {
		if (o == null)
			return false;

		if (o == this)
			return true;

		if (!o.getClass().equals(getClass()))
			return false;

		Element e = (Element) o;

		return this.getId() == e.getId() && this.getElementType() == e.getElementType();
	}

	public ArrayList<Street> getStreetList() {
		Injector injector = Guice.createInjector(new CoreModule());
		StreetDAO streetDAO = injector.getInstance(StreetDAOImpl.class);
		HashMap<Integer, Street> streets = streetDAO.getStreets();

		ArrayList<Street> streetList = new ArrayList<Street>(streets.values());

		return streetList;
	}

	/**
	 * @see Element#getConnectedStreets()
	 */
	public HashMap<NeighbourOrientation, Street> getConnectedStreets() {
		HashMap<NeighbourOrientation, Street> conStreets = new HashMap<NeighbourOrientation, Street>();

		ArrayList<Street> streetList = getStreetList();

		// for (Street street : streetList) {
		// if (street.getPredecessor().equals(this)) {
		// NeighbourOrientation or =
		// NeighbourOrientation.getRelativeNeighbourOrientation(this.getPosition(),
		// street.getSuccessor().getPosition());
		// conStreets.put(or, street);
		// }
		// if (street.getSuccessor().equals(this)) {
		// NeighbourOrientation or =
		// NeighbourOrientation.getRelativeNeighbourOrientation(this.getPosition(),
		// street.getPredecessor().getPosition());
		// conStreets.put(or, street);
		// }
		// }
		return conStreets;
	}

	public boolean isElementCrowded(NeighbourOrientation or) {
		// TODO Auto-generated method stub
		return false;
	}

	public int determineQuadrant(Element predecessor, Element successor) {
		int quadrant = 0;

		double xPre = predecessor.getX();
		double yPre = predecessor.getY();
		double xSuc = successor.getX();
		double ySuc = successor.getY();

		// Calculate linear equations

		double t1 = yPre - (1 * xPre); // y-Achsenabschnitt
		double t2 = yPre - (-1 * xPre); // y-Achsenabscnnitt

		double x1 = ySuc - t1; // Einsetzen in 1. Geradengleichung
		double x2 = -1 * (ySuc - t2); // Einsetzen in 2. Geradengleichung

		// 1. quadrant
		if (ySuc > yPre && xSuc < x1 && xSuc > x2) {
			quadrant = 1;
		}
		// 3. quadrant
		else if (ySuc < yPre && xSuc > x1 && xSuc < x2) {
			quadrant = 3;
		}
		// 2. quadrant
		else if (xSuc > xPre) {
			quadrant = 2;
		}
		// 4. quadrant
		else if (xSuc < xPre) {
			quadrant = 4;
		}
		return quadrant;
	}
	
	public List<Double2D> getConnections(Element src, Element dst, Street street, int quadrant) {
		List<Double2D> outConnectionList = new ArrayList<Double2D>();
		HashMap<Integer, Lane> outgoingLanes = new HashMap<Integer, Lane>();
		HashMap<Integer, Lane> incomingLanes = new HashMap<Integer, Lane>();
		
		double srcX = src.getX();
		double srcY = src.getY();
		
		Double2D a = new Double2D(srcX - 10, srcY + 10);
		Double2D b = new Double2D(srcX + 10, srcY + 10);
		Double2D c = new Double2D(srcX + 10, srcY - 10);
		Double2D d = new Double2D(srcX - 10, srcY - 10);
		System.out.println("a: " + a);
		System.out.println("b: " + b);
		System.out.println("c: " + c);
		System.out.println("d: " + d);
		
		// order lanes in incoming and outgoing:
		HashMap<Integer, Lane> lanes = street.getLanes();
		
		for(Lane lane : lanes.values()) {
			if(lane.getPredecessor().equals(src)) {
				outgoingLanes.put(lane.getId(), lane);
			}
			else if(lane.getPredecessor().equals(dst)) {
				incomingLanes.put(lane.getId(), lane);
			}
		}
		
		switch(quadrant) {
		case 1:
			break;
		case 2:
			break;
		case 3:
			break;
		case 4:
			break;
		}
		
		return outConnectionList;
	}
	
	public Element getPredecessor(ElementType fromType, int fromId) {
		if (fromType == ElementType.CENTROID) {
			return this.predecessor = centroidDAO.getCentroid(fromId);
		} else {
			return this.predecessor = intersectionDAO.getIntersection(fromId);
		}
	}

	public Element getSuccessor(ElementType toType, int toId) {
		if (toType == ElementType.CENTROID) {
			return this.successor = centroidDAO.getCentroid(toId);
		} else {
			return this.successor = intersectionDAO.getIntersection(toId);
		}
	}
	
//	public static void main(String[] args) throws JDOMException, IOException {
//		Intersection intersection = new IntersectionImpl3(0, 0, true, 30, 20);
//		Centroid centroid = new CentroidImpl(1, 0, 40, 40);
//		Lane lane1 = new LaneImpl(0, 0, ElementType.INTERSECTION, 1, ElementType.CENTROID, true);
//		Lane lane2 = new LaneImpl(1, 0, ElementType.INTERSECTION, 1, ElementType.CENTROID, true);
//		Lane lane3 = new LaneImpl(2, 0, ElementType.INTERSECTION, 1, ElementType.CENTROID, true);
//		Lane lane4 = new LaneImpl(3, 0, ElementType.INTERSECTION, 1, ElementType.CENTROID, true);
//		
//		HashMap<Integer, Lane> lanes = new HashMap<Integer, Lane>();
//		
//		lanes.put(0, lane1);
//		lanes.put(1, lane2);
//		lanes.put(2, lane3);
//		lanes.put(3, lane4);
//		
//		Street street = new StreetImpl(0, 0, lanes);
//		
//		int quadrant = determineQuadrant(intersection, centroid);
//		
//		getConnections(intersection, centroid, street, quadrant);
//	}
}
